/*Author: Rony Lahav (ID:043489889)*/

import java.io.Serializable;
import java.util.Collection;
import java.util.SortedMap;
import java.util.TreeMap;


/**
 * This class is the DB for all peer entries.
 * Allows inserting, deleting and updating a peer.
 */
public class DataBase implements Serializable{
	private static DataBase dbInstance;   // Singleton instance
	
	//we save all of the Peer Tuples in this map
	private SortedMap<String, DBTuple> tuplesMap = new TreeMap<String, DBTuple>();
	
	//we save all of the SN Tuples from snLists in this map
	private SortedMap<String, DBTuple> snListMap = new TreeMap<String, DBTuple>();
	
	private SortedMap<String, DBTuple> unconnectedList = new TreeMap<String, DBTuple>();
	
	//private CTOR
	private DataBase(){}
	
	// Singleton access
	public static DataBase getInstance(){
		if (dbInstance == null){
			dbInstance = new DataBase(); 
		}
		return dbInstance;
	}
	
	/**
	 * return all tuples in DB
	 */
	public synchronized SortedMap<String, DBTuple> getAllTuples(){
		return tuplesMap;
	}
	
	/**
	 * Return all Regular tuples in DB that are online
	 */
	public synchronized SortedMap<String, DBTuple> getAllOnlineRegTuples(){
		SortedMap<String, DBTuple> newMap = new TreeMap<String, DBTuple>();
	
		Collection<DBTuple> c = tuplesMap.values();
		
		for (DBTuple tuple : c) {
			// Check if the checked peer is online and that it is not me...
			if (tuple.getStatus() && !tuple.IsSN()) {
				newMap.put(tuple.getKey(), tuple);
			}
		}
		return newMap;
	}
	
	/**
	 * Return all main SN tuples in DB that are online (in order to propagate data to next SNs)
	 */
	public synchronized SortedMap<String, DBTuple> getOnlineSNTuplesForPropagation(){
		SortedMap<String, DBTuple> newMap = new TreeMap<String, DBTuple>();
	
		Collection<DBTuple> c = snListMap.values();
		String myKey = final_project.getMyKey();
		for (DBTuple snTuple : c) {
			// Check if SN is online and that it is connected through me and it's not me
			if (snTuple.getStatus() && snTuple.ConnectedThrough().equals(myKey) && !snTuple.getKey().equals(myKey)) {
				newMap.put(snTuple.getKey(), snTuple);
			}
		}
		return newMap;
	}
	
	public synchronized void disconnectSNsOfOfflineSN(DBTuple sn){		
		Collection<DBTuple> c = snListMap.values();
		String snKey = sn.getKey();
		for (DBTuple snTuple : c) {
			// Check if SN is online and that it is connected through me and it's not me
			if (snTuple.ConnectedThrough().equals(snKey)) {
				snTuple.setConnectedThrough("0");
			}
		}
	}
	
	public synchronized DBTuple getNextUnconnectedSNTuple(){
		Collection<DBTuple> c = snListMap.values();
		for (DBTuple snTuple : c) {
			// get all SNs connected through SN with snKey
			if (snTuple.getConnectedThrough().equals("0") && !unconnectedList.containsKey(snTuple.getKey())) {
				unconnectedList.put(snTuple.getKey(), snTuple);
				return snTuple;
			}
		}
		return null;
	}
	
	public void clearUnconnectedList(){
		unconnectedList.clear();
	}
	/**
	 * Return my main SN tuple
	 */
	public synchronized SortedMap<String, DBTuple> getMyMainSNTuple(){
		SortedMap<String, DBTuple> newMap = new TreeMap<String, DBTuple>();
	
		DBTuple mainSN = final_project.getMyMainSN();
		newMap.put(mainSN.getKey(), mainSN);

		return newMap;
	}
	
	/**
	 * Return all SN tuples in DB that are online
	 */
	public synchronized SortedMap<String, DBTuple> getAllOnlineSNTuples(){
		SortedMap<String, DBTuple> newMap = new TreeMap<String, DBTuple>();
	
		Collection<DBTuple> c = snListMap.values();
		
		for (DBTuple tuple : c) {
			// Check if the checked peer is online and that it is not me...
			if (tuple.getStatus() && tuple.IsSN()) {
				newMap.put(tuple.getKey(), tuple);
			}
		}
		return newMap;
	}
	
	/**
	 * Return all SN tuples in DB
	 */
	public synchronized SortedMap<String, DBTuple> getAllSNTuples(){
		SortedMap<String, DBTuple> newMap = new TreeMap<String, DBTuple>();
	
		Collection<DBTuple> c = snListMap.values();
		
		for (DBTuple tuple : c) {
			// Check if the checked peer is online and that it is not me...
			if (tuple.IsSN()) {
				newMap.put(tuple.getKey(), tuple);
			}
		}
		return newMap;
	}
	
	/**
	 * Return all regular tuples in DB
	 */
	public synchronized SortedMap<String, DBTuple> getAllRegTuples(){
		SortedMap<String, DBTuple> newMap = new TreeMap<String, DBTuple>();
	
		Collection<DBTuple> c = tuplesMap.values();
		
		for (DBTuple tuple : c) {
			// Check if the checked peer is online and that it is not me...
			if (!tuple.IsSN()) {
				newMap.put(tuple.getKey(), tuple);
			}
		}
		return newMap;
	}
	
	/**
	 * update a Tuple in the DB
	 */
	public synchronized void updateTuple(DBTuple tuple) {
		// Remove the old Tuple, if it exists
		if (tuplesMap.containsKey(tuple.getKey()))
			tuplesMap.remove(tuple.getKey());

		// Insert the tuple
		tuplesMap.put(tuple.getKey(), tuple);
	}
	
	public synchronized void updateMainSN(DBTuple mainSN){
		final_project.setMyMainSN(mainSN);
	}
	
	public synchronized void updateExistingTuple(DBTuple tuple) {
		// Remove the old Tuple, if it exists
		if (tuplesMap.containsKey(tuple.getKey())){
			tuplesMap.remove(tuple.getKey());

			// Insert the tuple
			tuplesMap.put(tuple.getKey(), tuple);
		}
	}
	
	/**
	 * insert a Tuple into the DB
	 */
	public synchronized void insertTuple(DBTuple tuple) {
		tuplesMap.put(tuple.getKey(), tuple);
	}

	/**
	 * @return all the tuples in the DB - in TEXT
	 */
	public String asString() {
		String sb = "";
		for (DBTuple tuple : tuplesMap.values()) {
			sb += (MessageCenter.newline + "_______________________" + MessageCenter.newline) + (tuple.toString());
		}

		return sb;
	}

	/**
	 * Returns true if the DB is empty, else otherwise
	 */
	public synchronized boolean isDBEmpty() {
		return tuplesMap.isEmpty();
	}
	
	/**
	 * Cleans the DB
	 */
	public synchronized void cleanDB(){
		if (tuplesMap!=null)
			tuplesMap.clear();
		if(snListMap!=null)
			snListMap.clear();
	}
	
	/**
	 * setting the last connection time to now
	 */
	public synchronized void setLastConnectionTime(String key){
		DBTuple tuple = tuplesMap.get(key);
		if(tuple == null){
			return;
		}
		tuple.setLastConnectionTime(System.currentTimeMillis());
	}
	
	/**
	 * mark peer as offline
	 * @param peerName
	 */
	public synchronized void setPeerOffline(String key){
		DBTuple tuple = tuplesMap.get(key);
		if(tuple == null){
			return;
		}
		tuple.setStatus("0");
	}
	
	/**
	 * mark peer as online
	 * @param peerName
	 */
	public synchronized void setPeerOnline(String key){
		DBTuple tuple = tuplesMap.get(key);
		if(tuple == null){
			return;
		}
		tuple.setStatus("1");
	}
	
	// return the DB Tuple of the peer
	public synchronized DBTuple findTuple(String key){
		return tuplesMap.get(key);
	}
	
	public synchronized SortedMap<String, DBTuple> getSnList(){
		if (snListMap.isEmpty())
			return null;
		return snListMap;
	}
	
	public synchronized void updateSN(DBTuple sn) {
		// Remove the old Tuple, if it exists
		if (final_project.getMyKey()==null || !sn.getKey().equals(final_project.getMyKey())){	//don't insert myself
			//if tuple exists and holds different data than the new one replace them because the new one's data is newer (from a "fresh" SNLIST)
			boolean existingSN = snListMap.containsKey(sn.getKey());
			if (existingSN && !sn.ConnectedThrough().equals(snListMap.get(sn.getKey()).ConnectedThrough())){
				snListMap.remove(sn.getKey());
				snListMap.put(sn.getKey(), sn);
			}
			else if(!existingSN)
			// Insert the new tuple
				snListMap.put(sn.getKey(), sn);
		}
	}
	
	public synchronized void insertSN(DBTuple sn){
		if (final_project.getMyKey()==null || !sn.getKey().equals(final_project.getMyKey())){	//don't insert myself
			if (!snListMap.containsKey(sn.getKey()))
				snListMap.put(sn.getKey(), sn);
		}
	}
	
	public synchronized DBTuple findSN(String key){
		return snListMap.get(key);
	}
	
	public synchronized void setLastSNConnectionTime(String key){
		DBTuple sn = snListMap.get(key);
		if(sn == null){
			return;
		}
		sn.setLastConnectionTime(System.currentTimeMillis());
	}
	
	public synchronized void setSNOffline(String key){
		DBTuple sn = snListMap.get(key);
		if(sn == null){
			return;
		}
		sn.setStatus("0");
	}
	
	public synchronized void setSNOnline(String key){
		DBTuple sn = snListMap.get(key);
		if(sn == null){
			return;
		}
		sn.setStatus("1");
	}
	
	private static final long serialVersionUID = -2487765432167654329L;
	
}
